#tag.py
#Timothy Sassone
#Defines the structure and functionality
#of the tag/SOML system.

import re

str_legal_tag = r"(\t*)\[([^\]\]\t\n]*)\]"
re_legal_tag = re.compile(str_legal_tag)

class Tag(object):
    def __init__(self, p, n, d=[]):
        """Object Initializer - parent, name, data-array"""
        if(p != None): #If we've been passed a parent (as opposed to None)
            if(type(p) != Tag): #If the passed parent is not a Tag object
                print("Parent is not of type Tag") #say so
                self.parent = None #and set the parent to None
            else: #If it is a Tag object
                self.parent.add_child(self)#add this tag as a child to it.
        else: #If we've not been passed a valid parent
            self.parent = None #Set parent to None (root tag)
            
        self.children = [] #Initialize children array as empty.

        if(type(n) != str): #If the name isn't a string
            print("Name must be a string!") #Complain
            self.name = "N/A" #and set it to "N/A"
        else: #if it is a string
            self.name = n; #set the name to it
        
        self.data = [] #Initialize the data array as empty
        self.data = d[:] #Copy the data from the argument into the array

    def get_children(self, name, data=[]):
        """Returns a list of children which fit the passed string (name+data)"""
        clist = [] #Empty list to store the children in
        for c in self.children: #Loop through all the children
            matches = True #Assume a match till something goes wrong
            if(c.name != name): #if the names don't match
                matches = False #the tag isn't a match
            else: #if the names do match...
                if(len(data)>len(c.data)): #if we're requiring more data than
                                           #the tag has
                    matches = False #The tag can't possibly match
                else: #Otherwise
                    for i in range(0, len(data)): #Loop through all the data
                        if(c.data[i]!=data[i]): #If it doesn't match the coresponding
                                                #data in the tag...
                            matches = False #The tag doesn't match
            if(matches): #If nothing has gone wrong so far
                clist.append(c) #Add the child to the list
        return clist #Return the list of children

    def add_child(self, c):
        """Add a child to a tag"""
        if(type(c) == Tag): #If the new child is a tag
            self.children.append(c) #Add it to my child list
            c.parent = self #And set myself as it's parent
        else: #If it's not a tag
           print("Child is not of type Tag.") #Complain

    def add_data(self, d):
        """Add data to a tag - self explanitory"""
        self.data.append(d)

    def find_tags(self, path):
        """Returns a list of all tags which fit the path."""
        tags = path.replace("\\","/").split("/") #Split the path on / and \
        ctext = tags[0] #Get the text we're working with (first item in the path)
        found = [] #Create an array for what we've found
        tagstr = "/".join(tags[1:]) #Create a new path of all remaining
                                    #path elements (I.E. A/B/C becomes B/C)

        ctext = ctext.split(":") #Split the text we pulled on colons
        cname = ctext[0] #The first is the name
        cdata = ctext[1:] #If there are any others, they're data requirements
        
        clist = self.get_children(cname, cdata) #Get a list of children which fit
        for c in clist: #For each child found
            if(len(tags)==1): #If the number of elements left in tags is 1,
                              #meaning we've reached the end of the path.
                found.append(c) #Add child directly to the list
            else: #If we're not at the end of the path
                found += c.find_tags(tagstr) #Search the next tag for the
                                             #remaining path.
        return found #Return whatever we've found

    def show_tree(self, prefix=""):
        """Print out the tree structure"""
        print(prefix + self.name + ": " + str(self.data)) #Print this tag
        for c in self.children: #For each of it's children
            c.show_tree("\t" + prefix) #Call this method in them with extra tab.

    def parse_tag(self, tstr):
        """Turns a single line of text into a Tab object"""
        tabs = 0 #Used to store the number of preceeding tabs
        match = re_legal_tag.match(tstr) #Test for a match
        if(match!=None): #If we found a match
            tabs = len(match.group(1)) #The first group is the tabs, count them
            data = match.group(2).split(":") #Split the second group on colons
            new_tag = Tag(None, data[0], data[1:]) #Create a tag with that info.
            return new_tag, tabs #Return the new tag, and the number of tabs.
        else: #If it's not a regex match
            return None, 0 #Return none and 0.

    def load_tags(self, fname):
        """Loads a set of tags from a file."""
        try:
            file = open(fname) #Open the file
        except IOError as e:
            print("Error: File not found (" + fname + ")")
            return None
        
        tag_stack = [] #Create a list for the stack of tags

        for line in file: #Loop through the lines in the file
            tag, tabs = self.parse_tag(line) #Try to parse the line into a tag
            if(tag!=None): #If it parsed correctly
                if(tabs==0): #if there were no preceeding tabs (root tag)
                    tag_stack = [] #Clear the tag stack
                    self.add_child(tag) #Add this as a child of the root
                    tag_stack.append(tag) #This is now the top of the stack
                elif(tabs < len(tag_stack)): #If it's fewer tabs than the
                                             #most recent
                    while(tabs < len(tag_stack) ): #Loop until there are fewer
                        tag_stack.pop() #And pop one off the stack each iteration
                    tag_stack[-1].add_child(tag) #Then add this as a child to the
                                                 #tag on the end of the list.
                    tag_stack.append(tag) #This is now the top of the stack
                else: #If it isn't a root tag or has fewer tabs than the
                      #most recent
                    tag_stack[-1].add_child(tag) #Add this as a child to the tag
                                                 #on the end of the list.
                    tag_stack.append(tag) #This  is now the top of the stack.
        print("File loaded (" + fname + ")")
